package com.demo.common.ratelimit.aspect;

import com.demo.common.ratelimit.annotation.DistributeLock;
import com.demo.common.ratelimit.exception.RateLimitException;
import com.demo.common.ratelimit.lock.RedisLock;
import com.demo.common.ratelimit.utils.*;
import com.demo.common.redis.component.RedisKeys;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;

@Order(2)
@Slf4j
@Aspect
public class DistributeLockAspect {

    @Resource
    public RedisLock redisLock;

    @Pointcut("@annotation(com.demo.common.ratelimit.annotation.DistributeLock)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取 使用注解的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        DistributeLock distributeLock = method.getDeclaredAnnotation(DistributeLock.class);
        //获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //获取请求的方法名
        String methodName = joinPoint.getSignature().getName();

        String extraValue = null;
        try {
            if (StringUtils.hasText(distributeLock.suffixKey())) {
                extraValue = SpelUtil.generateKeyBySpEL(distributeLock.suffixKey(), joinPoint, null);
            }
        } catch (Exception e) {
            log.error("幂等性EL表达式解析异常",e);
            throw new RateLimitException("幂等性EL表达式解析异常",e);
        }

        String lockKey = RedisKeys.getRedisDistributeLockKey(className, methodName,extraValue);
        String lockValue = RandomUtil.getUUID();

        // 获取锁
        boolean tryLock1 = redisLock.tryLock(lockKey, lockValue, distributeLock.expireTime(), distributeLock.timeUnit());
        if (tryLock1) {
            // 放行
            return performBiz(joinPoint,distributeLock.release(),lockKey, lockValue);
        }else {
            // 没有获取到锁就等待
            long millis = TimeoutUtils.toMillis(distributeLock.waitTime(), distributeLock.timeUnit());
            log.info("我睡眠了");

            boolean tryLock2 = SleepUtil.untilGetTrue(millis,
                    () -> redisLock.tryLock(lockKey, lockValue, distributeLock.expireTime(), distributeLock.timeUnit())
            );

            if (tryLock2) {
                return performBiz(joinPoint,distributeLock.release(),lockKey, lockValue);
            }else {
                throw new RateLimitException("系统繁忙，请稍后操作");
            }
        }

    }

    private Object performBiz(ProceedingJoinPoint joinPoint, boolean release,String lockKey, String lockValue) throws Throwable {
        Object proceed = joinPoint.proceed();
        if (release) {
            releaseLock(lockKey, lockValue);
        }
        return proceed;
    }

    private void releaseLock(String lockKey, String lockValue) {
        String s = redisLock.get(lockKey);
        if (Objects.nonNull(s) && Objects.equals(s, lockValue)) {
            redisLock.releaseLock(lockKey, lockValue);
        }
    }

}
